package lion.dev.encrypt;

import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Signature;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

import javax.crypto.Cipher;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang.StringUtils;

/**
 * @author lion
 * @mail hl_0074@sina.com
 * @date 2011-12-28
 * @des RSA 可以用于数字签名和加密，具有两把密钥，公钥和私钥 ,公钥给乙方，私钥保留,<br/>
 *      2,甲方用私钥对数据签名，将签名和据数发送给乙方 <br/>
 *      3,乙方跟据签名对数据进行验证，使用公钥解密<br/>
 *      4,乙方使用公钥对数据进行加密后发送给甲方 <br/>
 *      5，甲方使用私钥对数据进行解密
 * @eg:
 */
public class RSA {

	public static final String SIGNATURE_ALGORITHM_SHA1WithRSA = "SHA1withRSA";
	public static final String SIGNATURE_ALGORITHM_SHA256WithRSA = "SHA256withRSA";

	private static final String ALGORITHM_RSA = "RSA";
	private static final int KEY_SIZE = 1024;
	private PublicKey publicKey;
	private PrivateKey privateKey;
	private String signAlg = SIGNATURE_ALGORITHM_SHA1WithRSA;

	public void generateKey(String seed) {

		KeyPairGenerator keygen = null;
		try {
			keygen = KeyPairGenerator.getInstance(ALGORITHM_RSA);
		} catch (NoSuchAlgorithmException e) {
			throw new IllegalArgumentException("", e);
		}
		SecureRandom secureRandom = null;
		if (StringUtils.isNotBlank(seed)) {
			secureRandom = new SecureRandom(seed.getBytes());
		} else {
			secureRandom = new SecureRandom();
		}
		keygen.initialize(KEY_SIZE, secureRandom);
		KeyPair keyPair = keygen.generateKeyPair();

		// 公钥
		publicKey = keyPair.getPublic();
		// 私钥
		privateKey = keyPair.getPrivate();
	}

	public void generateKey() {

		this.generateKey(null);
	}

	public String getPublicKey() {

		return Base64.encodeBase64String(publicKey.getEncoded());
	}

	public String getPrivateKey() {

		return Base64.encodeBase64String(privateKey.getEncoded());
	}

	public byte[] encrypt(byte[] data) {

		return doFinal(Cipher.ENCRYPT_MODE, data, this.publicKey);
	}

	public byte[] decrypt(byte[] data) {

		return doFinal(Cipher.DECRYPT_MODE, data, this.privateKey);
	}

	public byte[] sign(byte[] data) {

		return sign(data, this.privateKey, this.signAlg);
	}

	public boolean verify(byte[] data, String sign) {

		return verify(data, Base64.decodeBase64(sign));
	}

	public boolean verify(byte[] data, byte[] sign) {

		return verify(data, this.publicKey, sign, this.signAlg);
	}

	public String getSignAlg() {

		return signAlg;
	}

	public void setSignAlg(String signAlg) {

		this.signAlg = signAlg;
	}

	public void setPubKey(String key) {

		this.publicKey = parsePublicKey(key);
	}

	public void setPrivateKey(String key) {

		this.privateKey = parsePrivateKey(key);
	}

	/**
	 * 公钥加密
	 */
	public static byte[] encryptByPublickKey(byte[] data, String key) {

		// 取得公钥
		Key publicKey = parsePublicKey(key);
		return doFinal(Cipher.ENCRYPT_MODE, data, publicKey);
	}

	/**
	 * 私钥解密
	 */
	public static byte[] decryptByPrivateKey(byte[] data, String key) {

		// 取得私钥
		Key privateKey = parsePrivateKey(key);
		return doFinal(Cipher.DECRYPT_MODE, data, privateKey);
	}

	/**
	 * 私钥签名
	 */
	public static byte[] sign(byte[] data, String key, String signAlg) {

		PrivateKey priKey = parsePrivateKey(key);
		if (priKey != null) { return sign(data, priKey, signAlg); }
		return null;
	}

	private static byte[] sign(byte[] data, PrivateKey key, String signAlg) {

		byte[] result = null;
		try {
			// 对数据签证
			Signature signature = Signature.getInstance(signAlg);
			signature.initSign(key);
			signature.update(data);
			return signature.sign();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return result;
	}

	/**
	 * 公钥验证
	 */
	public static boolean verify(byte[] data, String publicKey, String sign, String signAlg) {

		// 取公钥匙对象
		PublicKey pubKey = parsePublicKey(publicKey);

		if (pubKey != null) { return verify(data, pubKey, Base64.decodeBase64(sign), signAlg); }

		return false;
	}

	public static boolean verify(byte[] data, PublicKey pubKey, byte[] sign, String signAlg) {

		try {
			Signature signature = Signature.getInstance(signAlg);
			signature.initVerify(pubKey);
			signature.update(data);
			// 验证签名是否正常
			return signature.verify(sign);
		} catch (Exception e) {
			e.printStackTrace();
		}

		return false;
	}

	public static byte[] doFinal(int mode, byte[] data, Key key) {

		byte[] result = null;
		try {
			// 对数据加密
			Cipher cipher = Cipher.getInstance(ALGORITHM_RSA);
			cipher.init(mode, key);
			result = cipher.doFinal(data);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return result;
	}

	public static PublicKey parsePublicKey(String key) {

		byte[] keyBytes = Base64.decodeBase64(key);

		// 取得公钥
		X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);

		KeyFactory keyFactory = null;
		try {
			keyFactory = KeyFactory.getInstance(ALGORITHM_RSA);
			return keyFactory.generatePublic(x509KeySpec);
		} catch (Exception e) {
			e.printStackTrace();
		}

		return null;
	}

	public static PrivateKey parsePrivateKey(String key) {

		// 对私钥解密
		byte[] keyBytes = Base64.decodeBase64(key);
		PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
		KeyFactory keyFactory;
		try {
			keyFactory = KeyFactory.getInstance(ALGORITHM_RSA);
			return keyFactory.generatePrivate(pkcs8KeySpec);
		} catch (Exception e) {
			e.printStackTrace();
		}

		return null;
	}

	/**
	 * load the .cer file
	 */
	public void loadCertificate(String path) throws Exception {

		FileInputStream fis = new FileInputStream(path);

		loadCertificate(fis);
	}

	public void loadCertificate(byte[] cert) throws Exception {

		ByteArrayInputStream bis = new ByteArrayInputStream(cert);

		loadCertificate(bis);
	}

	public void loadCertificate(InputStream cert) throws Exception {

		CertificateFactory certificateFactory;
		try {
			certificateFactory = CertificateFactory.getInstance("X.509");
			X509Certificate certificate = (X509Certificate) certificateFactory.generateCertificate(cert);
			this.publicKey = certificate.getPublicKey();
			this.signAlg = certificate.getSigAlgName();
		} catch (Exception e) {
			throw new Exception("加载证书失败", e);
		} finally {
			try {
				cert.close();
			} catch (Exception e) {
			}
		}
	}

	public String getSingAlgorithm() {

		return this.signAlg;
	}
}
